package com.wallace.downloadserver.util.myToken

import com.sun.xml.internal.ws.developer.Serialization
import com.wallace.downloadserver.bean.User
import com.wallace.downloadserver.redis.RedisManager
import com.wallace.downloadserver.redis.setV
import com.wallace.downloadserver.util.myEncrypt.AESCryptUtil
import com.wallace.downloadserver.util.myEncrypt.AESKey
import com.wallace.downloadserver.util.myJson.JsonUtil
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

/**
 * <Description>
 * 自定义token的实现和管理:
 * 生成AES密钥
 * 用AES加密user信息（uid和role），放入redis中存储，
 * 有效时间和刷新机制视具体使用场景而定。
 * </Description>
 * @author Wallace
 * @Date 2022/1/12 15:42
 */
@Component
object TokenUtil {
    private val log: Logger = LoggerFactory.getLogger(TokenUtil::class.java)

    const val TYPE_NETTY = "Netty"

    /**
     * 限制令牌的默认有效时间，5分钟
     */
    val LIMIT_TOKEN_TIME = TimeUnit.SECONDS.toSeconds(5 * 60L)

    /**
     * 普通令牌的默认有效时间，30分钟
     */
    val COMMON_TOKEN_TIME = TimeUnit.SECONDS.toSeconds(30 * 60L)

    /**
     * 服务端重启，可能会使AES密钥更新，导致前AES密钥签发的token不可用
     */
    private val aesKey: AESKey by lazy {
        AESCryptUtil.generateKey()
    }

    private var redisTokenDB: RedisTemplate<String, Any>? = null

    @Autowired
    private fun setRedisTokenDB(redisManager: RedisManager) {
        redisTokenDB = redisManager.getTokenDB()
    }

    /**
     * 签发token
     * @param user token要包含的内容
     * @param interfaces token使用的接口
     * @param type token类型
     */
    fun signToken(user: User, interfaces: List<String>, type: String): String? {
        val tokenKey = type + "-" + user.uid + "-" + user.role
        val tokenContent = TokenContent(tokenKey, interfaces)
        val jsonString = JsonUtil.toJson(tokenContent)
        val tokenString = jsonString?.let { AESCryptUtil.encryptAndEncodeToString(it, aesKey.getSecretKeySpec(), aesKey.iv) }
        tokenString?.let { redisTokenDB!!.setV(key = tokenKey, value = it, time = LIMIT_TOKEN_TIME) }
        return tokenString
    }

    @Synchronized
    fun signNettyToken(user: User, path: List<String>): String? {
        return signToken(user, path, TYPE_NETTY)
    }

    /**
     * 验证token,解密token成功，返回解密后的信息
     * @param token 待验证和解密的token字符串
     * @return 返回解密后的信息
     */
    fun verifyToken(token: String): TokenContent? {
        val jsonString = AESCryptUtil.decryptAfterDecodeToString(token, aesKey.getSecretKeySpec(), aesKey.iv)
        return jsonString?.let { JsonUtil.toAny(it, TokenContent::class.java) }
    }

    /**
     * 验证该token是否有权限调用这些接口
     * @param token 待验证的token
     * @param interfaceNames 待验证的接口
     * @return Boolean
     */
    fun verifyToken(token: String, interfaceNames: List<String>): Boolean {
        val tokenContent = verifyToken(token)
        if (tokenContent != null) {
            interfaceNames.forEach {
                if (it !in tokenContent.interfaces) {
                    return false
                }
            }
            return true
        }
        return false
    }
}

/**
 * @param id TokenContent唯一标识，id = "type-uid-role"
 * @param interfaces 该token适用的接口名称
 * @param timestamp 创建token时刻的时间戳，目的是使得token有变化
 * eg1（限制令牌）:
 * ["/downloadserver/api/login/logout","/downloadserver/api/preRequest/preRequest"]
 * eg2(通用令牌):
 * ["all"]
 */
@Serialization
data class TokenContent(val id: String, val interfaces: List<String>, val timestamp: Long = System.currentTimeMillis())